﻿using System;
using System.Collections.Generic;
using System.Text;

using System.IO;
using System.Threading;
using System.Runtime.Serialization.Formatters.Binary;
using System.Net;

namespace RemoteObject
{
    [Serializable]
    public class DownloadJob
    {
        public List<Downloader> downloaderList = new List<Downloader>();
        public List<BlockInfo> blockInfoList = new List<BlockInfo>();
        public FileStream fs_net;
        private String fnLocal;
        private String fnRemote;
        private int fileLength;
        private Common.Status jobStatus;
        private List<String> serverUrlList;

        public DownloadJob(String fnLocal, String fnRemote, String serverUrl)
        {
            this.fnLocal = fnLocal;
            this.fnRemote = fnRemote;
            this.serverUrlList = new List<string>();
            this.serverUrlList.Add(serverUrl);
            fileLength = 0;
            jobStatus = Common.Status.Pause;

        }

        public void UpdateProgressFile()
        {
            lock (this)
            {
                if (fs_net != null)
                {
                    fs_net.SetLength(0);
                    Common.bf.Serialize(fs_net, blockInfoList);
                }
            }
        }

        public void DownloaderFinish(Downloader _downloader)
        {
            lock (this)
            {
                int activeDownloaders = 0;
                foreach (Downloader downloader in downloaderList)
                {
                    activeDownloaders += downloader.ThreadFinished ? 0 : 1;
                }
                if (activeDownloaders == 0)
                {
                    if (fs_net != null)
                    {
                        fs_net.Close();
                        fs_net = null;
                        //bf = null;
                        //String.Format("tcp://{0}:10001/RemoteObject.MyRemoteObject", Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString())
                        serverUrlList.Add(String.Format("tcp://{0}:10001/RemoteObject.MyRemoteObject", Dns.GetHostEntry(Dns.GetHostName()).AddressList[0].ToString()));
                        jobStatus = Common.Status.Completed;
                        Console.WriteLine("Download Job Threads all finished ---------------");
                    }
                }
            }
        }

        // TODO, [Vincent], This is an ergent compromize to clear 'MyThread'. As Thread Object could not be Serializable.
        private void StartAllDownloaders(List<Thread> threadList)
        {
            lock (this)
            {
                if (downloaderList.Count != 0 && threadList.Count != 0)
                {
                    fs_net = new FileStream(fnLocal + ".net", FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite);

                    foreach (Thread obj in threadList)
                    {
                        obj.Start();
                    }
                    threadList.Clear();
                }
                else
                {
                    // Probally that, Job begins with Breakpoint resuming. So No Downloader Thread at all, mark as completed.
                    jobStatus = Common.Status.Completed;
                }
            }
        }

        public void StartDownload()
        {
            if (jobStatus == Common.Status.Working || jobStatus == Common.Status.Completed) return;

            jobStatus = Common.Status.Working;
            RemoteObject.MyRemoteObject proxyObjectTcp = (RemoteObject.MyRemoteObject)Activator.GetObject(typeof(RemoteObject.MyRemoteObject), GetServerURL());
            fileLength = (int)proxyObjectTcp.GetFileLength(fnRemote);

            proxyObjectTcp = null;

            //Considering Breakpoint Resuming
            bool isBreakpointResuming = false;
            List<Thread> threadList = new List<Thread>();
            if (File.Exists(fnLocal + ".net") && File.Exists(fnLocal))
            {
                FileStream net_fs_temp = new FileStream(fnLocal + ".net", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                if (net_fs_temp.Length != 0)
                {
                    // breakpoint resume
                    isBreakpointResuming = true;
                    Console.WriteLine("Break Point Resuming");
                    blockInfoList = Common.bf.Deserialize(net_fs_temp) as List<BlockInfo>;

                    downloaderList = new List<Downloader>(Common.MAX_BLOCKS);
                    foreach (BlockInfo obj in blockInfoList)
                    {
                        if (obj.remainDataLength > 0)
                        {
                            Downloader downloader = new Downloader(fnRemote, fnLocal, fileLength, obj, this);
                            Thread thread = new Thread(new ThreadStart(downloader.Run));
                            threadList.Add(thread);
                        }
                        //PrintProgress(obj);
                    }
                    this.StartAllDownloaders(threadList);
                }
                net_fs_temp.Close();
            }

            if (!isBreakpointResuming)
            {
                // Net Task Begin
                Console.WriteLine("New Task Begin");

                //create the temp local file with full size
                if (File.Exists(fnLocal))
                {
                    File.Delete(fnLocal + ".old");
                    File.Move(fnLocal, fnLocal + ".old");
                }

                FileStream fs = new FileStream(fnLocal, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite);
                fs.SetLength(fileLength);
                fs.Close();

                File.Delete(fnLocal + ".old");

                int startPos;
                int dataLength;
                int blockLength = (int)(fileLength + Common.MAX_BLOCKS - 1) / Common.MAX_BLOCKS;
                downloaderList = new List<Downloader>(Common.MAX_BLOCKS);

                for (int i = 0; i < Common.MAX_BLOCKS; i++)
                {
                    startPos = blockLength * i;
                    dataLength = Math.Min(blockLength, fileLength - startPos);
                    BlockInfo blockInfo = new BlockInfo();
                    blockInfo.blockID = i;
                    blockInfo.originPos = startPos;
                    blockInfo.currentPos = startPos;
                    blockInfo.remainDataLength = dataLength;

                    blockInfoList.Add(blockInfo);

                    Downloader downloader = new Downloader(fnRemote, fnLocal, fileLength, blockInfo, this);
                    Thread thread = new Thread(new ThreadStart(downloader.Run));
                    threadList.Add(thread);
                }

                this.StartAllDownloaders(threadList);
            }
        }

        public void StopDownload()
        {
            foreach (Downloader downloader in downloaderList)
            {
                downloader.StopDownload();
            }
            jobStatus = Common.Status.Pause;
        }

        public void RestartDownload()
        {
            StopDownload();
            Thread.Sleep(1000);
            StartDownload();
        }

        public String GetJobName()
        {
            return fnLocal.Substring(fnLocal.LastIndexOf("\\") + 1);
        }

        public float GetProgress()
        {
            float progress = 0;
            foreach (Downloader downloader in downloaderList)
            {
                progress += downloader.GetProgress();
            }
            progress /= Common.MAX_BLOCKS;
            if (progress == 100) jobStatus = Common.Status.Completed;
            return progress;
        }

        public float GetDownloadSpeed()
        {
            float speed = 0;
            foreach (Downloader downloader in downloaderList)
            {
                speed += downloader.GetDownloadSpeed();
            }
            return speed;
        }

        public float GetFileSizeInMB()
        {
            return fileLength / 1024.0f / 1024.0f;
        }

        public Common.Status GetStatus()
        {
            return jobStatus;
        }

        public List<Downloader> GetDownloaderList()
        {
            return downloaderList;
        }

        public String GetServerURL()
        {
            // TODO: [Vincent] We temporary return teh first element of the List.
            // Would expand the Host List Later.
            foreach (string str in serverUrlList)
            {
                if (str.Contains(Common.CloudServerURL)) return Common.CloudServerURL;
            }
            return serverUrlList[0];
        }

        public List<String> GetServerURLs()
        {
            return serverUrlList;
        }
    }
}
